//
//  StarsViewGLAnimator.swift
//  HLDesktop
//
//  Created by 黄龙 on 2023/11/8.
//

//实现将一个View分解成碎片下落的动效，类似玻璃打碎下落效果
import UIKit
import GLKit
//在settings>Preprocessor Macros设置GLES_SILENCE_DEPRECATION=1，消除GLKit的相关警告

struct TexturedVertex {
    var geometryVertex = Vector2()
    var textureVertex = Vector2()
}

struct TexturedQuad {
    var bl = TexturedVertex()
    var br = TexturedVertex() { didSet { _br = br } }
    var tl = TexturedVertex() { didSet { _tl = tl } }
    var tr = TexturedVertex()
    
    // openGL optimization. it uses triangles to draw.
    // so we duplicate 2 vertex, so it have 6 vertex to draw two triangles
    fileprivate var _br = TexturedVertex()
    fileprivate var _tl = TexturedVertex()
}

struct Sprite {
    var quad = TexturedQuad()
    var moveVelocity = Vector2()
    
    mutating func slice(_ rect: CGRect, textureSize: CGSize) {
        quad.bl.geometryVertex = Vector2(x: 0, y: 0)
        quad.br.geometryVertex = Vector2(x: rect.size.width, y: 0)
        quad.tl.geometryVertex = Vector2(x: 0, y: rect.size.height)
        quad.tr.geometryVertex = Vector2(x: rect.size.width, y: rect.size.height)
        
        quad.bl.textureVertex = Vector2(x: rect.origin.x / textureSize.width, y: rect.origin.y / textureSize.height)
        quad.br.textureVertex = Vector2(x: (rect.origin.x + rect.size.width) / textureSize.width, y: rect.origin.y / textureSize.height)
        quad.tl.textureVertex = Vector2(x: rect.origin.x / textureSize.width, y: (rect.origin.y + rect.size.height) / textureSize.height)
        quad.tr.textureVertex = Vector2(x: (rect.origin.x + rect.size.width) / textureSize.width, y: (rect.origin.y + rect.size.height) / textureSize.height)
        
        position += Vector2(rect.origin)
    }
    
    var position = Vector2() {
        didSet {
            let diff = position - oldValue
            quad.bl.geometryVertex += diff
            quad.br.geometryVertex += diff
            quad.tl.geometryVertex += diff
            quad.tr.geometryVertex += diff
        }
    }
    
    mutating func update(_ tick: TimeInterval) {
        position += moveVelocity * Float32(tick)
    }

}


//在build settings>Preprocessor Macros设置GLES_SILENCE_DEPRECATION=1，消除GLKit的相关警告
class StarsViewGLAnimator: NSObject {
    var duration: TimeInterval = 2
    var spriteWidth: CGFloat = 8
    var xSmallDiff:CGFloat = -100 //x方向偏移位区间
    var xBigDiff:CGFloat = 100
    fileprivate var sprites: [Sprite] = []
    fileprivate var glContext: EAGLContext!
    fileprivate var effect: GLKBaseEffect!
    fileprivate var glView: GLKView?
    fileprivate var displayLink: CADisplayLink!
    fileprivate var lastUpdateTime: TimeInterval?
    fileprivate var render: SpriteRender!
    
    func animateCrushView(crushView:UIView,In backView:UIView) {
        self.glContext = EAGLContext(api: .openGLES2)
        EAGLContext.setCurrent(glContext)
        glView = GLKView(frame: crushView.frame, context: glContext)//碎裂后显示的区域
        if let glV = self.glView{
            glV.enableSetNeedsDisplay = true
            glV.delegate = self
            glV.isOpaque = false
            backView.addSubview(glV) //碎裂显示在哪个view上
        }
        let texture = ViewTexture()
        texture.setupOpenGL()
        texture.render(view: crushView) //要碎裂的view的copy
        
        effect = GLKBaseEffect()
        let projectionMatrix = GLKMatrix4MakeOrtho(0, Float(texture.width), 0, Float(texture.height), -1, 1)
        effect.transform.projectionMatrix = projectionMatrix
        
        render = SpriteRender(texture: texture, effect: effect)
        
        let size = CGSize(width: CGFloat(texture.width), height: CGFloat(texture.height))
        
        let scale = UIScreen.main.scale
        let width = spriteWidth * scale
        let height = width
        
        for x in stride(from: CGFloat(0), through: size.width, by: width) {
//stride是 Swift 标准库中的一个函数,用于在一个范围内生成等间隔的值序列
//for index in stride(from:0, to:3, by:1)       //0,1,2
//for index in stride(from:0, through:3, by:1)  //0,1,2,3 for index in stride(from: 0, through: 9, by: 3) //0,3,6,9
            for y in stride(from: CGFloat(0), through: size.height, by: height) {
                let region = CGRect(x: x, y: y, width: width, height: height)
                var sprite = Sprite()
                sprite.slice(region, textureSize: size)
                sprite.moveVelocity = Vector2(x: SwiftFunc.randomFloatBetween(xSmallDiff, and: xBigDiff), y: SwiftFunc.randomFloatBetween(-CGFloat(texture.height)*1.2/CGFloat(duration), and: -CGFloat(texture.height)/CGFloat(duration)))

                sprites.append(sprite)
            }
        }
        crushView.removeFromSuperview()
        
        displayLink = CADisplayLink(target: self, selector: #selector(displayLinkTick(_:)))
        displayLink.isPaused = false
        displayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common)
        self.perform(#selector(animationEnded), with: nil, afterDelay: duration)
    }
    
    @objc func animationEnded() {
        displayLink.invalidate()
        displayLink = nil
        if let glV = glView {
            glV.removeFromSuperview()
            glView = nil
        }
    }
    
    @objc func displayLinkTick(_ displayLink: CADisplayLink) {
        if let lastUpdateTime = lastUpdateTime {
            let timeSinceLastUpdate = Date.timeIntervalSinceReferenceDate - lastUpdateTime
            self.lastUpdateTime = Date.timeIntervalSinceReferenceDate
            for index in 0..<sprites.count {
                sprites[index].update(timeSinceLastUpdate)
            }
        } else {
            lastUpdateTime = Date.timeIntervalSinceReferenceDate
        }
        if let glv = glView{
            glv.setNeedsDisplay()
        }
    }
}

extension StarsViewGLAnimator: GLKViewDelegate {
    func glkView(_ view: GLKView, drawIn rect: CGRect) {
        glClearColor(0, 0, 0, 0)
        glClear(UInt32(GL_COLOR_BUFFER_BIT))
        glBlendFunc(GLenum(GL_SRC_ALPHA), GLenum(GL_ONE_MINUS_SRC_ALPHA))
        glEnable(GLenum(GL_BLEND))
        
        render.render(self.sprites)
    }
}

class ViewTexture {
    var name: GLuint = 0
    var width: GLsizei = 0
    var height: GLsizei = 0
    
    func setupOpenGL() {
        glGenTextures(1, &name)
        glBindTexture(GLenum(GL_TEXTURE_2D), name)
        
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GLint(GL_LINEAR))
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GLint(GL_LINEAR))
        
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GLint(GL_CLAMP_TO_EDGE))
        glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GLint(GL_CLAMP_TO_EDGE))
        glBindTexture(GLenum(GL_TEXTURE_2D), 0);
    }
    
    deinit {
        if name != 0 {
            glDeleteTextures(1, &name)
        }
    }
    
    func render(view: UIView) {
        let scale = UIScreen.main.scale
        width = GLsizei(view.layer.bounds.size.width * scale)
        height = GLsizei(view.layer.bounds.size.height * scale)
        
        var texturePixelBuffer = [GLubyte](repeating: 0, count: Int(height * width * 4))
        let colorSpace = CGColorSpaceCreateDeviceRGB()
        
        withUnsafeMutablePointer(to: &texturePixelBuffer[0]) { texturePixelBuffer in
            let context = CGContext(data: texturePixelBuffer,
                width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: Int(width * 4), space: colorSpace,
                bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)!
            context.scaleBy(x: scale, y: scale)
            
            UIGraphicsPushContext(context)
            view.drawHierarchy(in: view.layer.bounds, afterScreenUpdates: false)
            UIGraphicsPopContext()
            
            glBindTexture(GLenum(GL_TEXTURE_2D), name);
            
            glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GLint(GL_RGBA), width, height, 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), texturePixelBuffer)
            glBindTexture(GLenum(GL_TEXTURE_2D), 0);
        }
    }
}


class SpriteRender {

    fileprivate let texture: ViewTexture
    fileprivate let effect: GLKBaseEffect
    
    init(texture: ViewTexture, effect: GLKBaseEffect) {
        self.texture = texture
        self.effect = effect
    }
    
    func render(_ sprites: [Sprite]) {
        effect.texture2d0.name = self.texture.name
        effect.texture2d0.enabled = 1
        
        effect.prepareToDraw()
        
        var vertex = sprites.map { $0.quad }
        
        glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
        glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
        
        withUnsafePointer(to: &vertex[0].bl.geometryVertex) { offset in
            glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 2, GLenum(GL_FLOAT), GLboolean(UInt8(GL_FALSE)), GLsizei(MemoryLayout<TexturedVertex>.size), offset)
        }
        withUnsafePointer(to: &vertex[0].bl.textureVertex) { offset in
            glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue), 2, GLenum(GL_FLOAT), GLboolean(UInt8(GL_FALSE)), GLsizei(MemoryLayout<TexturedVertex>.size), offset)
        }

        glDrawArrays(GLenum(GL_TRIANGLES), 0, GLsizei(vertex.count * 6))
    }
}
